home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
9-Digit Zip Code Directory
/
9-Digit Zip Code Directory (American Business Information) (ABIZIP-12).ISO
/
z4src.zip
/
BSCFG.C
< prev
next >
Wrap
C/C++ Source or Header
|
1995-09-20
|
76KB
|
2,500 lines
//----------------------------------------------------------------------------
// MODULE DESCRIPTION
//
// Module: bscfg.c
// Title: Base library
// Notice: John M. Weeder
// Copyright (c) 1993. All rights reserved.
// This module contains proprietary information and should be
// treated as confidential.
//
//----------------------------------------------------------------------------
// MAINTENANCE HISTORY
//
// $Workfile$
// $Revision$
// $Author$
// $Date$
// $Log$
//
//----------------------------------------------------------------------------
// MODULE NARRATIVE
//
//
// This module contains code to manage a program configuration file.
// Each program has a configuration file opened automatically at startup.
// If it does not exist, the file is created in the same directory as the
// executable or in the current directory. Configuration files have an
// extension of '.cfg'
//
// A configuration file consists of an arbitrary number of named data items.
// Each data item is called a KEY.
//
// A key entry currently has a maximum length of 256. There are 17 bytes of
// overhead in the key (including null terminator on name). Allowing room
// for a minimum of 4 bytes of data in the key, there is a maximum key name
// length of 256 - 25 - 4 = 227 bytes.
//
// Each configuration file has a file header. This header contains some text
// information which may be viewed by 'type'ing of 'cat'ing the file. The
// header also contains some version information, the location of the root node
// of the data tree, and the start of the linked list of empty blocks.
//
// The remainder of the file is divided into a series of arbitrary length
// BLOCKs. Each block contain a short header which has the size of the block,
// the amount of data in the block, and links for the empty block list. Blocks
// follow, one after the other, until the end of the file. All unused blocks
// are maintained in a linked list.
//
// The data keys are stored in a b-plus tree. Each node in the tree has a
// fixed size. Each key element has a maximum size. Each nodes contains as
// many keys as possible, with no key exceeding its maximum size. The maximum
// depth of the tree is limited by storage for the search pointers. Data for
// each key is stored within the key element if it will fit, otherwise it is
// stored in a separate data block. Keys are inserted with a method that
// garuantees a balanced tree, however deleting keys can cause an imbalance.
// For this reason, deletions should be limited.
//
// The insertion method is simple. Work down the tree. If the current node
// is full, split it. A node is full when it does not have at least KEY_SIZE
// bytes free. Note that the max number of keys in a node is not fixed, if
// the names and data are small, a large number of keys may fit into a node.
// The minimum number of keys is fixed at NODE_SIZE / KEY_SIZE.
//
// This code is not generally re-entrant. Global data has been placed in a
// structure in case multiple configuration files are desired at some future
// date. A semaphore is used to resolve mutual access conflicts under OS/2.
//
// Naming conventions:
// Names must be <= MAX_CFG_NAME bytes in length.
// Names should include type information. For example: 'John~STRING' is
// a string named John.
// The type information should follow the name and be separated with a
// tilde. Multiple elements of the name should be sepated by a tilde.
// Example:
// object1~STRUCT~object2~INT An integer named object2 in a
// structure named object1.
//
// This naming order is used to that similarly named object (for instance,
// all objects in a structure) are stored in the same locality.
//
// NOTES:
// Disk structures should always use 'SHORT' and 'LONG' and other fixed
// size data types. NEVER use things such as INT and SIZET. Also, be
// very careful of structure packing. To be safe, try to align data on
// 4 byte boundaries.
// Yeah, nodes waste a lot of space in overhead, but they are simpler
// to maintain than if everything was bit packed into the structure.
//
// The code in this module should be written entirely in C.
// Do not use any C++ constructs.
//
// This module is portable to:
// DOS 3.X+
// MS Windows 3.X+
// OS/2 2.X+
// OS/2 2.0 PM
// SCO UNIX.
//
// The following compilers are supported:
// MSC 6.0A
// MSC/C++ 7.0
// Borland C++ 3.1 for DOS
// Borland C++ 1.0 for OS/2 2.X
// SCO UNIX cc
//
//----------------------------------------------------------------------------
#include <bs.h>
//----------------------------------------------------------------------------
// Constants and data types
//----------------------------------------------------------------------------
#define HDR_SIZE (256) // Size of file header
#define HDR_VER (0x0100) // Version
#define HDR_ID (0x6168DCBAL) // File id
#define BLK_SIZE (32) // Minimin block size
// All blocks are some multiple of this
#define NODE_SIZE (2048) // Size of a b-plus tree node
#define KEY_SIZE (256) // Key size information
// Actual size of key structure - padding
#define KEY_OVERHEAD (sizeof(CFGKEY)-3)
// Internal maximum name length
#define KEY_NAME ((KEY_SIZE - KEY_OVERHEAD) - sizeof(LONG))
#define MAX_LEVEL (5) // Maximum level of b-plus tree
// This allows for up to 512K entries!
//
// The cache for the nodes is allocated dynamically and as
// needed.
//
// Under DOS, the cache shrinks to a minimum level when not in
// use. Normally, this minimum level is 2. The maximum cache size
// is the MAX_LEVEL + 1. The extra cache buffer is used when a node is
// split.
//
#define MAX_CACHE (MAX_LEVEL + 1)
#if OS_DOS
# define MIN_CACHE (2)
#else
# define MIN_CACHE (MAX_CACHE)
#endif
#if OS_UNIX
# define LF "\n"
#else
# define LF "\r\n"
#endif
#define CTRLZ "\x1A"
typedef struct _CFGHDR // Configuration file header
{
CHAR szText[200]; // Header description
LONG lId; // Identifier word
LONG lRevision; // Revision count
FPOS fposRoot; // Position of root node
FPOS fposEmpty; // Address of first empty block or 0
USHORT usVer; // File version
BYTE bUnused[56 - 4 - 2 - 4 - 4 - 4];
} CFGHDR; // This structure should have
BASETYPE(CFGHDR); // a size of HDR_SIZE bytes
typedef struct _CFGBLK // Data block header
{
LONG lSize; // Total size of block
LONG lData; // Size of data area
CRC crcData; // CRC of data
FPOS fposNext; // Address of next and previous
FPOS fposPrev; // empty blocks.
SHORT fEmpty; // Block is empty?
SHORT sUnused; // Used to pad to 4 byte boundary
} CFGBLK; // Block header
BASETYPE(CFGBLK);
typedef struct _CFGKEY // B-Plus tree key
{
LONG cb; // Total size of this key
LONG cbData; // Size of data
FPOS fposData; // Position of data block or 0
LONG fLast; // Last key in this node
FPOS fposLeft; // Position of left and right child
FPOS fposRight; // nodes. Only last key in the node
// can have a right child
CHAR szName[4]; // Name of key.
// 1 byte is for null, 3 for padding
} CFGKEY; // This structure is variable length
BASETYPE(CFGKEY);
typedef struct _CFGGBL // Global data.
{
BOOL fInit; // Initialized flag
CFGHDR cfghdr; // Current file header
BOOL fHeaderDirty; // Is file header changed?
SIZET cLock; // Lock count
TID tid; // Thread id of locker
HF hf; // Config file handle
PBYTE apbCache[MAX_CACHE]; // Cache buffer pointers
BOOL afCacheUsed[MAX_CACHE]; // Cache buffer used?
BOOL afCacheDirty[MAX_CACHE]; // Cache buffer dirty?
FPOS afposCache[MAX_CACHE]; // Disk offset of cache buffer
LONG alCacheUse[MAX_CACHE]; // Disk offset of cache buffer
LONG lCacheUse; // Usage counter for cache
SIZET cCacheMin; // Minimum cache buffers
PCFGKEY apcfgkeyLevel[MAX_LEVEL]; // Pointer to current key in node
SIZET acLevelCache[MAX_LEVEL]; // Cache buffer id for node
SIZET cLevel; // Levels read in (first level is root)
LONG lRevision; // Last header revision
// Used to know when to flush cache
CHAR szName[MAX_CFG_NAME]; // Last name found!
#if OS_OS2 || OS_PM
BOOL fSemaphore; // Semaphore not created yet!!
HMTX hmtx; // Access semaphore
#endif
} CFGGBL;
BASETYPE(CFGGBL);
#define CfgKeyNext(x) ((PCFGKEY)(((PBYTE)(x)) + (SIZET)(x)->cb))
#define CfgNodeBuf(x) (pg->apbCache[pg->acLevelCache[(x)]])
#define CfgNodeDirty(x) pg->afCacheDirty[pg->acLevelCache[(x)]] = TRUE
#define CfgNodeKey(x) (pg->apcfgkeyLevel[(x)])
#define CfgNodeLevel() (pg->cLevel - 1)
#define CfgNodePos(x) (pg->afposCache[pg->acLevelCache[(x)]])
#define CfgNodeSetKey(x,y) (pg->apcfgkeyLevel[(x)] = (y))
#define CfgNodeUsed(x,y) (pg->afCacheUsed[pg->acLevelCache[(x)]] = (y))
//----------------------------------------------------------------------------
// Globals
//----------------------------------------------------------------------------
static CHAR szHeaderFormat[] = // File header format text
"Program Configuration File v%d.%02d" LF
"%s" LF
"Created %2d:%02d:%02d %2d/%02d/%4d by %s" LF
CTRLZ;
//
// Global data is always accessed via a pointer. This allows easy conversion
// to multiple file management in the future.
//
static CFGGBL g; // Global data.
static PCFGGBL pg = &g; // Pointer to global data.
//----------------------------------------------------------------------------
// Prototypes
//----------------------------------------------------------------------------
#if COMPILER_DEBUG
static VOID FN_L CfgBlockIsAddrValid(FPOS);
static VOID FN_L CfgBlockWalk(BOOL);
#endif
static LONG FN_L CfgBlockDataSize(LONG);
static BOOL FN_L CfgBlockMarkEmpty(FPOS);
static BOOL FN_L CfgBlockRead(FPOS, PCFGBLK);
static LONG FN_L CfgBlockTotalSize(LONG);
static BOOL FN_L CfgBlockUseEmpty(FPOS);
static FPOS FN_L CfgBlockWrite(FPOS, PCFGBLK);
static BOOL FN_L CfgCacheFindEmpty(PSIZET);
static BOOL FN_L CfgCacheFlush(SIZET);
static BOOL FN_L CfgCacheFlushAll(void);
static VOID FN_L CfgCacheInvalidate(void);
static BOOL FN_L CfgCacheShrink(void);
static BOOL FN_L CfgCreate(PCSZ);
static BOOL FN_L CfgDataRead(FPOS, PCFGBLK, PBYTE _FAR_ *, PSIZET);
static FPOS FN_L CfgDataWrite(FPOS, PCVOID, LONG);
static BOOL FN_L CfgHeaderRead(void);
static BOOL FN_L CfgHeaderWrite(BOOL);
static SIZET FN_L CfgKeyCount(SIZET, PSIZET);
static BOOL FN_L CfgKeyDelete(void);
static BOOL FN_L CfgKeyFind(PCSZ, PCFGKEY _FAR_ *);
static BOOL FN_L CfgKeyInsert(SIZET, PCFGKEY);
static BOOL FN_L CfgKeyNew(PCSZ, PCFGKEY _FAR_ *, SIZET);
static BOOL FN_L CfgKeyRemove(void);
static BOOL FN_L CfgKeySplit(void);
static BOOL FN_L CfgInitialize(void);
static BOOL FN_L CfgLock(void);
static BOOL FN_L CfgNodeNew(PSIZET);
static BOOL FN_L CfgNodeRead(FPOS);
static VOID FN_L CfgNodeReset(void);
static BOOL FN_L CfgUnlock(void);
//----------------------------------------------------------------------------
// Description: Compute size of data area of a block.
// Parameters: lSize Total size of block
// Returns: Size of blocks data area.
//----------------------------------------------------------------------------
static LONG FN_L CfgBlockDataSize(LONG lSize)
{
Assert(lSize > (LONG)sizeof(CFGBLK));
return lSize - (LONG)sizeof(CFGBLK);
}
//----------------------------------------------------------------------------
// Description: Check if a block address is valid. Address must be past
// the end of the file header and less than the end of the
// file. The address must also be a multiple of BLK_SIZE.
// Parameters: fpos Address of block
// Returns: Aborts if address is invalid
//----------------------------------------------------------------------------
#if COMPILE_DEBUG
static VOID FN_L CfgBlockIsAddrValid(FPOS fpos)
{
FPOS fsize = FileGetSize(pg->hf); // Get file size
if (fsize < 0L)
Fatal("Internal configuration file error.\nProblem reading file size.");
if (fpos >= fsize) // Address past end of file
Fatal("Internal configuration file error.\nRead/write past end of file.");
// Address before beginning of file
if (fpos < (FPOS)sizeof(CFGHDR))
Fatal("Internal configuration file error.\nRead/write before beginning of file.");
if (fpos % (FPOS)BLK_SIZE) // Address not on a correct boundary
Fatal("Internal configuration file error.\nRead/write not on correct boundary.");
return ;
}
#endif
//----------------------------------------------------------------------------
// Description: Mark block as empty. This block is combined with all
// empty blocks following immediately before and after it.
// The empty space in the block is zeroed for security reasons.
// The empty block is then inserted at the start of a linked
// list of empty blocks.
// Parameters: fpos Offset (in bytes) of block within the file.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgBlockMarkEmpty(FPOS fpos)
{
CFGBLK cfgblk1, cfgblk2;
FPOS fsize;
FPOS fposEmpty;
LONG lData;
fsize = FileGetSize(pg->hf); // Get file size
if (fsize < 0)
return FALSE;
//
// Read current block header.
// Read next block header. If block is empty, add it to this block. Keep
// going until all adjacent empty blocks are concatenated.
//
if (!CfgBlockRead(fpos, &cfgblk1))
return FALSE;
while (fpos + cfgblk1.lSize < fsize)
{ // Read next block header
if (!CfgBlockRead(fpos + cfgblk1.lSize, &cfgblk2))
return FALSE;
if (!cfgblk2.fEmpty) // If it's used, break
break;
// Otherwise, remove from empty chain
if (!CfgBlockUseEmpty(fpos + cfgblk1.lSize))
return FALSE;
cfgblk1.lSize += cfgblk2.lSize; // Combine with current block
}
//
// At this point, we know that any empty blocks immediately after the
// current block have been combined. We must now search for a block
// immediately before. If this is not done, the file eventually becomes
// a series of minimum sized empty blocks.
//
// NOTE: We only search for ONE preceeding block since that is all
// there can be.
//
fposEmpty = pg->cfghdr.fposEmpty;
while (fposEmpty)
{ // Read block
if (!CfgBlockRead(fposEmpty, &cfgblk2))
return FALSE;
// Check if this is the block
if (fposEmpty + cfgblk2.lSize == fpos)
{
LONG lData; // Yeah! Combine with new empty block
cfgblk2.lSize += cfgblk1.lSize;
lData = CfgBlockDataSize(cfgblk2.lSize);
// Zero out data area
if (!FileFill(pg->hf, lData, fposEmpty + (FPOS)sizeof(CFGBLK), 0))
return FALSE;
// Write changed block header
if (!CfgBlockWrite(fposEmpty, &cfgblk2))
return FALSE;
return TRUE;
}
fposEmpty = cfgblk2.fposNext; // Goto next block
}
//
// Else, link this block into the empty block chain.
//
cfgblk1.lData = 0;
cfgblk1.crcData = 0L;
cfgblk1.fEmpty = TRUE;
cfgblk1.fposPrev = 0L;
cfgblk1.fposNext = pg->cfghdr.fposEmpty;
// Zero out data area
lData = CfgBlockDataSize(cfgblk1.lSize);
if (!FileFill(pg->hf, lData, fpos + (FPOS)sizeof(CFGBLK), 0))
return FALSE;
if (!CfgBlockWrite(fpos, &cfgblk1)) // Write block header
return FALSE;
pg->cfghdr.fposEmpty = fpos; // Change link in file header
pg->fHeaderDirty = TRUE;
if (cfgblk1.fposNext) // Adjust pointer in next block
{
if (!CfgBlockRead(cfgblk1.fposNext, &cfgblk2))
return FALSE;
cfgblk2.fposPrev = fpos;
if (!CfgBlockWrite(cfgblk1.fposNext, &cfgblk2))
return FALSE;
}
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Read data block header.
// Parameters: fpos Offset (in bytes) of block within the file.
// pcfgblk Buffer for block header.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgBlockRead(FPOS fpos, PCFGBLK pcfgblk)
{
#if COMPILE_DEBUG
CfgBlockIsAddrValid(fpos); // Validate address
#endif
// Read block header
if (!FileRead(pg->hf, pcfgblk, sizeof(CFGBLK), fpos))
return FALSE;
#if COMPILE_DEBUG // Check for valid block sizes
if (pcfgblk->lSize < (LONG)sizeof(CFGBLK)
|| (pcfgblk->lSize % BLK_SIZE)
|| pcfgblk->lData < 0
|| pcfgblk->lData + (LONG)sizeof(CFGBLK) > pcfgblk->lSize)
Fatal("Internal configuration file error.\nCorrupt block header.");
#endif
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Compute the size of block needed to hold the requested
// amount of data. Block size is always rounded to a
// multiple of BLK_SIZE.
// Parameters: lData Data size.
// Returns: Required size of data block
//----------------------------------------------------------------------------
static LONG FN_L CfgBlockTotalSize(LONG lData)
{
Assert(lData >= 0);
lData += (LONG)sizeof(CFGBLK); // Add size of block header
if (lData % (LONG)BLK_SIZE) // Round to multiple of block size
lData += BLK_SIZE - (lData % (LONG)BLK_SIZE);
return lData;
}
//----------------------------------------------------------------------------
// Description: Use an empty block.
// Remove it from the chain of empty blocks.
// Parameters: fpos Offset (in bytes) of block within the file.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgBlockUseEmpty(FPOS fpos)
{
CFGBLK cfgblk;
if (!CfgBlockRead(fpos, &cfgblk)) // Read the block
return FALSE;
if (cfgblk.fposPrev) // Read previous if not header
{
CFGBLK cfgblkPrev;
if (!CfgBlockRead(cfgblk.fposPrev, &cfgblkPrev))
return FALSE;
// Update it's link
cfgblkPrev.fposNext = cfgblk.fposNext;
if (!CfgBlockWrite(cfgblk.fposPrev, &cfgblkPrev))
return FALSE;
}
else // Otherwise, update file header
{ // to point to next empty block
pg->cfghdr.fposEmpty = cfgblk.fposNext;
pg->fHeaderDirty = TRUE;
}
if (cfgblk.fposNext) // Read next block and update it
{
CFGBLK cfgblkNext;
if (!CfgBlockRead(cfgblk.fposNext, &cfgblkNext))
return FALSE;
// Update pointer
cfgblkNext.fposPrev = cfgblk.fposPrev;
// Write block
if (!CfgBlockWrite(cfgblk.fposNext, &cfgblkNext))
return FALSE;
}
cfgblk.fEmpty = FALSE; // Update block, mark used
cfgblk.fposNext = 0;
cfgblk.fposPrev = 0;
if (!CfgBlockWrite(fpos, &cfgblk)) // Write changes
return FALSE;
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Walk all the blocks in a file in an attempt to
// validate the file.
// Parameters: fShow If TRUE, display blocks to output as they are
// walked.
// Returns: Aborts on error.
//----------------------------------------------------------------------------
#if COMPILE_DEBUG
static VOID FN_L CfgBlockWalk(BOOL fShow)
{
FPOS fsize = FileGetSize(pg->hf);
FPOS fpos = (FPOS)sizeof(CFGHDR);
CFGBLK cfgblk;
if (fsize < (FPOS)sizeof(CFGHDR))
Fatal("Internal configuration file error.\nFile size is too small.");
while (fpos < fsize) // Walk through data blocks
{
if (!CfgBlockRead(fpos, &cfgblk))// Read header
return ;
if (fShow) // Show data
Output("Offset: %8ld Size: %8ld", fpos, cfgblk.lSize);
if (!cfgblk.fEmpty) // If not an empty block, read the
{ // data and verify the CRC
PBYTE pb = NULL;
SIZET cb = 0;
CRC crcData;
if (!CfgDataRead(fpos, &cfgblk, &pb, &cb))
return ;
crcData = CrcCalc(pb, cb); // Calc CRC
MemFree(pb); // Free buffer
if (fShow)
Output(" Data: (0x%08lX) Length = %ld\n", crcData, cfgblk.lData);
// Check CRC codes
if (crcData != cfgblk.crcData)
Fatal("Internal configuration file error.\nCRC does not verify.");
}
else if (fShow)
{
Output(" **EMPTY** Next: %8ld Prev: %8ld\n", cfgblk.fposNext, cfgblk.fposPrev);
}
fpos += (FPOS)cfgblk.lSize;
}
// We MUST end up exactly at the end of the file.
if (fpos > fsize)
Fatal("Internal configuration file error.\nFile size is invalid.");
return ;
}
#endif
//----------------------------------------------------------------------------
// Description: Write a block header to the file.
// Parameters: fpos Address to write block at.
// pcfgblk Pointer to block header to write.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static FPOS FN_L CfgBlockWrite(FPOS fpos, PCFGBLK pcfgblk)
{
#if COMPILE_DEBUG
CfgBlockIsAddrValid(fpos); // Validate address
#endif
// Read block header
if (!FileWrite(pg->hf, pcfgblk, sizeof(CFGBLK), fpos))
return FALSE;
pg->fHeaderDirty = TRUE; // Mark header as dirty to update rev
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Find or create an empty cache buffer.
// This function will never return a cache buffer allocated
// to one of the nodes in the current search path because they
// will have the highest usage counts.
// Parameters: pc Variable to receive offset of empty cache buffer
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgCacheFindEmpty(PSIZET pc)
{
SIZET i;
LONG lMin;
BOOL fFound = FALSE;
for (i = 0; i < MAX_CACHE; ++i) // Look for unused cache buffer
if (!pg->afCacheUsed[i])
{
if (pg->apbCache[i] == NULL) // If necessary, allocate cache buffer
{
pg->apbCache[i] = (PBYTE)MemAlloc(NODE_SIZE);
if (pg->apbCache[i] == NULL)
return FALSE;
}
*pc = i;
fFound = TRUE;
break;
}
if (!fFound)
{
lMin = pg->alCacheUse[0]; // Find cache element with the lowest
*pc = 0; // usage count. It is the oldest
for (i = 1; i < MAX_CACHE; ++i)
if (pg->alCacheUse[i] < lMin)
{
lMin = pg->alCacheUse[i];
*pc = i;
}
if (pg->afCacheDirty[*pc]) // If cache is dirty, flush it.
if (!CfgCacheFlush(*pc)) // This probable shouldn't ever happen
return FALSE;
}
#if COMPILE_DEBUG // Double check cache usage!
for (i = 0; i < pg->cLevel; ++i)
if (pg->acLevelCache[i] == *pc)
Fatal("Internal configuration file error.\nCache is corrupted.");
#endif
pg->afCacheUsed[*pc] = TRUE; // Already marked as used!
pg->afCacheDirty[*pc] = FALSE;
pg->afposCache[*pc] = (FPOS)-1L;
pg->alCacheUse[*pc] = ++pg->lCacheUse;
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Flush a single cache buffer
// Assumes that cache is valid and dirty
// Parameters: n Node to flush.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgCacheFlush(SIZET n)
{
PBYTE pb = pg->apbCache[n];
pg->afposCache[n] = CfgDataWrite(pg->afposCache[n], (PVOID)pb, NODE_SIZE);
if (pg->afposCache[n] <= 0)
return FALSE;
pg->afCacheDirty[n] = FALSE; // Cache is not dirty anymore
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Flush all cache buffers
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgCacheFlushAll(void)
{
BOOL fResult = TRUE;
SIZET i;
for (i = 0; i < MAX_CACHE; ++i)
if (pg->afCacheUsed[i] && pg->afCacheDirty[i])
if (!CfgCacheFlush(i))
fResult = FALSE;
return fResult;
}
//----------------------------------------------------------------------------
// Description: Invalidate cache contents
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static VOID FN_L CfgCacheInvalidate(void)
{
SIZET i;
for (i = 0; i < MAX_CACHE; ++i)
{
pg->afCacheUsed[i] = FALSE;
pg->afCacheDirty[i] = FALSE;
pg->afposCache[i] = (ULONG)-1;
pg->alCacheUse[i] = 0;
}
pg->lCacheUse = 0; // Reset usage count
return ;
}
//----------------------------------------------------------------------------
// Description: Shrink the number of allocated cache buffers. This is done
// for DOS only!
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgCacheShrink(void)
{
SIZET i;
for (i = pg->cCacheMin; i < MAX_CACHE; ++i)
if (pg->apbCache[i])
{
MemFree(pg->apbCache[i]);
pg->apbCache[i] = NULL;
pg->afCacheUsed[i] = FALSE;
}
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Close a configuration file.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgClose(void)
{
BOOL fResult = TRUE;
if (pg && pg->fInit)
{
SIZET i;
Assert(pg->cLock == 0); // File should not be locked!
if (!CfgUnlock()) // Unlock the file
fResult = FALSE;
for (i = 0; i < MAX_CACHE; ++i) // Free cache buffers
if (pg->apbCache[i])
MemFree(pg->apbCache[i]);
#if OS_OS2 || OS_PM
if (pg->fSemaphore) // Free semaphore
DosCloseMutexSem(pg->hmtx);
#endif
if (pg->hf >= 0) // Close file
if (!FileClose(pg->hf))
fResult = FALSE;
memset(pg, 0, sizeof(CFGGBL)); // Clear data structure
}
return fResult;
}
//----------------------------------------------------------------------------
// Description: Create a new configuration file.
// File is closed after being created.
// File is opened in exclusive access mode to prevent changes
// by other processes.
// Parameters: pcszFile Name of file
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgCreate(PCSZ pcszFile)
{
FLAG32 fl = FL_OPEN|FL_CREATE|FL_READWRITE|FL_DENYNONE|FL_BINARY;
time_t now = time(NULL);
struct tm *tm = localtime(&now);
//
// Verify file header is HDR_SIZE bytes in length
//
Assert(sizeof(CFGHDR) == HDR_SIZE);
Assert((sizeof(CFGHDR) % BLK_SIZE) == 0);
//
// Verify external maximum name length versus internal length
//
Assert(MAX_CFG_NAME == KEY_NAME);
//
// An integral number of keys must fit in a node and a node must have
// space for at least 3 keys. This is so that a split always has enough
// keys for a parent and two children. Splits always occur when a node
// is full.
//
Assert((NODE_SIZE % KEY_SIZE) == 0);
Assert(NODE_SIZE / KEY_SIZE >= 3);
Assert((NODE_SIZE % BLK_SIZE) == 0);
//
// Create a file header.
//
memset(&pg->cfghdr, 0, sizeof(pg->cfghdr));
sprintf(pg->cfghdr.szText,
szHeaderFormat,
(int)((HDR_VER >> 8) & 0x00FF),
(int)(HDR_VER & 0x00FF),
CfgGet(CFG_COPYRIGHT, NULL),
tm->tm_hour, tm->tm_min, tm->tm_sec,
tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
CommandLineAppName());
pg->cfghdr.lId = HDR_ID; // Set id and version
pg->cfghdr.usVer = HDR_VER;
pg->cfghdr.fposRoot = (FPOS)sizeof(CFGHDR);
//
// Write the file and an empty block for the root node
// apbCache[0] is gauranteed to be allocated at this time and is obviously
// not in use. It is used as a temporary data area for the root node.
//
if (FileOpen(&pg->hf, pcszFile, fl, NULL))
{
memset(pg->apbCache[0], 0, NODE_SIZE);
if (CfgHeaderWrite(TRUE)
&& CfgDataWrite((ULONG)-1L, pg->apbCache[0], NODE_SIZE) > 0)
{
FileClose(pg->hf);
pg->hf = -1;
Log(LOG_MEDIUM, "Created configuration file.\n'%s'", pcszFile);
return TRUE;
}
}
pg->hf = -1;
FileClose(pg->hf);
return FALSE;
}
//----------------------------------------------------------------------------
// Description: Read data from block
// Parameters: fpos Offset (in bytes) of block within the file.
// pcfgblk Block header.
// If NULL, the block header is read in.
// ppb Variable to receive address of data.
// If null, a buffer is allocated for the data.
// The caller is responsible for freeing the data
// buffer.
// pcb Variable to receive size of data.
// If not null, the initial value of this variable
// should be the size of the existing data data buffer
// or 0 for the maximum size.
// If null, size is not returned.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgDataRead(FPOS fpos, PCFGBLK pcfgblk, PBYTE _FAR_ *ppb, PSIZET pcb)
{
CFGBLK cfgblk;
SIZET cb;
BOOL fAlloc = FALSE;
if (pcfgblk == NULL) // Read block in if needed
{
pcfgblk = &cfgblk;
if (!CfgBlockRead(fpos, &cfgblk))
return FALSE;
}
cb = (SIZET)pcfgblk->lData; // Allocate buffer for data
if (pcb && *pcb)
cb = MIN(*pcb, cb);
Assert(cb);
if (*ppb == NULL) // Allocate a buffer
{
*ppb = (PBYTE)MemAlloc(cb);
if (*ppb == NULL)
return FALSE;
fAlloc = TRUE;
}
fpos += sizeof(CFGBLK); // Read data
if (!FileRead(pg->hf, *ppb, cb, fpos))
{
if (fAlloc) // Free buffer if we allocated it
MemFree(*ppb);
return FALSE;
}
if (pcb) // Return size
*pcb = cb;
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Write a block to the file.
// Parameters: fpos Address to write block at.
// If fpos <= 0L, find an unused block and write
// the data to it.
// If fpos > 0, check the current size of the
// block, and if we don't fit, find a newer, larger
// block. Else, just use the current block.
// pvData Pointer to data buffer
// lData Size of the data to write.
// May not == 0
// Returns: Position block was written at, or -1 for error.
//----------------------------------------------------------------------------
static FPOS FN_L CfgDataWrite(FPOS fpos, PCVOID pvData, LONG lData)
{
CFGBLK cfgblk;
FPOS fsize = FileGetSize(pg->hf);
LONG lNeeded;
if (fsize < (FPOS)sizeof(CFGHDR))
Fatal("Internal configuration file error.\nFile size is too small.");
Assert(pvData && lData);
lNeeded = CfgBlockTotalSize(lData); // Get required block size
//
// Read current block. If we can't fit into the current block, mark it
// as empty.
//
if (fpos > 0L)
{
if (!CfgBlockRead(fpos, &cfgblk))
return (ULONG)-1L;
if (cfgblk.lSize < lNeeded) // If it doesn't fit,
{ // Mark as empty
if (!CfgBlockMarkEmpty(fpos))
return (ULONG)-1L;
fpos = (ULONG)-1L; // Set position to -1 to force search
} // for new location
}
//
// Search for an unused block.
//
if (fpos <= 0L && pg->cfghdr.fposEmpty)
{
fpos = pg->cfghdr.fposEmpty; // Search the list of empties
while (fpos)
{
if (!CfgBlockRead(fpos, &cfgblk))
return (FPOS)-1L;
if (cfgblk.lSize >= lNeeded) // Check if it will fit here
{ // Remove from empty chain
if (!CfgBlockUseEmpty(fpos))
return FALSE;
break;
}
fpos = cfgblk.fposNext; // Move to next empty block
}
}
//
// Allocate an empty block at the end of the file.
//
if (fpos <= 0L)
{
if (!FileSetSize(pg->hf, (FPOS)(fsize + (FPOS)lNeeded)))
return (FPOS)-1L;
pg->fHeaderDirty = TRUE; // File is now changed!
// Lengthen file.
memset(&cfgblk, 0, sizeof(cfgblk));
cfgblk.lSize = lNeeded;
fpos = fsize; // Set position and new file size
fsize += lNeeded;
}
//
// If this block is extra long, divide it in two pieces. The second
// chunk is an empty block. Leave a little free space at the end of this
// block in case it grows.
//
lNeeded += 4 * BLK_SIZE;
if (lNeeded < cfgblk.lSize)
{
CFGBLK cfgblkEmpty;
FPOS fposEmpty = fpos + lNeeded;
// Write a block header
memset(&cfgblkEmpty, 0, sizeof(cfgblkEmpty));
cfgblkEmpty.lSize = cfgblk.lSize - lNeeded;
// Write temporary block header
if (!CfgBlockWrite(fposEmpty, &cfgblkEmpty))
return (FPOS)-1L;
// Then mark block as empty
if (!CfgBlockMarkEmpty(fposEmpty))
return (FPOS)-1L;
cfgblk.lSize = lNeeded; // Adjust size of current block
}
//
// Now, write the data.
// A CRC is saved along with each block to validate it.
//
cfgblk.lData = lData;
cfgblk.fEmpty = FALSE;
cfgblk.crcData = CrcCalc((PBYTE)pvData, (SIZET)lData);
cfgblk.fposPrev = 0L;
cfgblk.fposNext = 0L;
if (!CfgBlockWrite(fpos, &cfgblk)) // Write header
return (FPOS)-1L;
// Write data
if (!FileWrite(pg->hf, pvData, (SIZET)lData, fpos + (FPOS)sizeof(CFGBLK)))
return (FPOS)-1L;
pg->fHeaderDirty = TRUE; // Mark header as dirty to update rev
return fpos;
}
//----------------------------------------------------------------------------
// Description: Delete an object from a configuration file.
// Parameters: pcszName Name of the object to write.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgDelete(PCSZ pcszName)
{
PCFGKEY pcfgkey;
BOOL fResult = FALSE;
Assert(pcszName && strlen(pcszName) <= KEY_NAME);
if (!CfgLock())
return FALSE;
if (CfgKeyFind((PSZ)pcszName, &pcfgkey))
if (pcfgkey != NULL)
{
if (CfgKeyDelete())
fResult = TRUE;
}
else
Error("Configuration file key not found\n'%s'", pcszName);
if (!CfgUnlock())
fResult = FALSE;
return fResult;
}
//----------------------------------------------------------------------------
// Description: Find a object in the configuration file.
// Parameters: pcszName Key name to find.
// Returns: TRUE if successful and key found.
//----------------------------------------------------------------------------
BOOL FN_E CfgFind(PCSZ pcszName)
{
PCFGKEY pcfgkey;
BOOL fResult = FALSE;
Assert(pcszName && strlen(pcszName) <= KEY_NAME);
if (!CfgLock())
return FALSE;
if (CfgKeyFind((PSZ)pcszName, &pcfgkey))
if (pcfgkey != NULL)
fResult = TRUE;
if (!CfgUnlock())
fResult = FALSE;
return fResult;
}
//----------------------------------------------------------------------------
// Description: Terminate current find first/next query. This function
// releases locks on the database so that others can use it.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgFindClose(void )
{
return CfgUnlock();
}
//----------------------------------------------------------------------------
// Description: Find first name in configuration file.
// NOTE: CfgFindClose() must be called at the conclusion of
// a search or other users will not be able to access
// the file!
// Parameters: pszName Buffer for object name. The buffer should be
// of size MAX_CFG_NAME + 1.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgFindFirst(PSZ pszName)
{
FPOS fposNode = pg->cfghdr.fposRoot;
PCFGKEY pcfgkey;
Assert(pszName);
if (!CfgLock())
return FALSE;
CfgNodeReset(); // Start reading at the top
while (fposNode) // While more nodes to search
{ // Read this node
if (!CfgNodeRead(fposNode))
break;
pcfgkey = CfgNodeKey(CfgNodeLevel());
if (pcfgkey->cb == 0) // No first key, root is empty
break;
if (pcfgkey->fposLeft == 0) // Leaf?
{
strcpy(pszName, pcfgkey->szName);
strcpy(pg->szName, pcfgkey->szName);
return TRUE;
}
fposNode = pcfgkey->fposLeft; // Work down left side of tree
}
CfgUnlock();
return FALSE; // Never gets here
}
//----------------------------------------------------------------------------
// Description: Find next name in configuration file.
// NOTE: CfgFindClose() must be called at the conclusion of
// a search or other users will not be able to access
// the file!
// CfgFindClose() is automatically called after the
// last key.
// Parameters: pszName Buffer for object name. The buffer should be
// of size MAX_CFG_NAME + 1.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgFindNext(PSZ pszName)
{
SIZET cLevel;
PCFGKEY pcfgkey;
FPOS fpos;
Assert(pszName);
if (pg == NULL // File should already be locked
|| !pg->fInit
|| pg->cLock == 0
|| pg->tid != THREADID
|| pg->hf < 0)
return FALSE;
// Find previous name
if (!CfgKeyFind(pg->szName, &pcfgkey)
|| pcfgkey == NULL)
goto ERROR_EXIT;
cLevel = CfgNodeLevel(); // Get pointers to previous key
pcfgkey = CfgNodeKey(cLevel);
if (!pcfgkey->fLast) // If it wasn't the last key in it's
{ // node, then move to the next key.
pcfgkey = CfgKeyNext(pcfgkey);
if (pcfgkey->fposLeft) // If that key has a left child,
{ // recurse into it.
FPOS fposNode = pcfgkey->fposLeft;
while (fposNode)
{ // Read a level deeper
if (!CfgNodeRead(fposNode))
goto ERROR_EXIT;
// Find first key in node
pcfgkey = CfgNodeKey(CfgNodeLevel());
if (pcfgkey->fposLeft == 0)// If it is a leaf, return name
goto SUCCESS_EXIT;
fposNode = pcfgkey->fposLeft;
}
}
goto SUCCESS_EXIT; // Next key in leaf node
}
if (pcfgkey->fposRight) // Last key, if it has a right child,
{ // move down to the child
FPOS fposNode = pcfgkey->fposRight;
while (fposNode) // Then work down the left side of the
{ // child's sub-tree
if (!CfgNodeRead(fposNode))
goto ERROR_EXIT;
// If it's a lead, use it
pcfgkey = CfgNodeKey(CfgNodeLevel());
if (pcfgkey->fposLeft == 0)
goto SUCCESS_EXIT;
fposNode = pcfgkey->fposLeft;
}
}
do // We are at a right most leaf key
{
if (!cLevel)
goto ERROR_EXIT;
fpos = CfgNodePos(cLevel); // Move up until we are not a right
cLevel--; // most leaf key and not the root.
pcfgkey = CfgNodeKey(cLevel);
}
while (fpos == pcfgkey->fposRight);
SUCCESS_EXIT: // Store names and exit
strcpy(pszName, pcfgkey->szName);
strcpy(pg->szName, pcfgkey->szName);
return TRUE;
ERROR_EXIT:
CfgUnlock(); // No more keys, release and return
return FALSE;
}
//----------------------------------------------------------------------------
// Description: Read configuration file header.
// File should already be locked
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgHeaderRead(void)
{ // Read the header
if (!FileRead(pg->hf, (PVOID)&pg->cfghdr, sizeof(pg->cfghdr), 0L))
return FALSE;
if (pg->cfghdr.lId != HDR_ID) // Check id and version
return Error("Internal configuration file error.\nHeader id is incorrect.");
if (pg->cfghdr.usVer != HDR_VER)
return Error("Internal configuration file error.\nUnrecognized format.\n.");
pg->fHeaderDirty = FALSE;
// If file has changed, invalidate cache
if (pg->lRevision != pg->cfghdr.lRevision)
CfgCacheInvalidate();
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Write configuration file header if dirty.
// File should already be locked
// A revision counter is updated before writing. This counter
// is checked when the header is re-read, if it has changed,
// then another process has modified the data file and the
// cache's are invalidated.
// Parameters: fForce If true, force header to be written.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgHeaderWrite(BOOL fForce)
{
if (pg->fHeaderDirty || fForce)
{
pg->cfghdr.lRevision++;
pg->lRevision = pg->cfghdr.lRevision;
if (!FileWrite(pg->hf, (PVOID)&pg->cfghdr, sizeof(pg->cfghdr), 0L))
return FALSE;
pg->fHeaderDirty = FALSE;
}
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Initialize global variables.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgInitialize(void)
{
SIZET i;
memset(pg, 0, sizeof(CFGGBL)); // Zero just to be safe
pg->fInit = TRUE; // Set initialize flag right away!
pg->hf = -1;
pg->cCacheMin = MIN_CACHE; // Minimum cache size
for (i = 0; i < MIN_CACHE; ++i) // Allocate minimum number of cache
{ // buffers
pg->apbCache[i] = (PBYTE)MemAlloc(NODE_SIZE);
if (pg->apbCache[i] == NULL)
return FALSE;
}
#if OS_OS2 || OS_PM
if (DosCreateMutexSem(NULL, &pg->hmtx, 0, 0))
return FALSE;
pg->fSemaphore = TRUE;
#endif
CfgCacheInvalidate(); // Invalidate the cache
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Count the keys on a particular level.
// Parameters: cLevel Level to count keys in.
// pcb If not null, the total size of the keys is returned
// in this variable.
// Returns: Number of keys in node
//----------------------------------------------------------------------------
static SIZET FN_L CfgKeyCount(SIZET cLevel, PSIZET pcb)
{
PCFGKEY pcfgkey = (PCFGKEY)CfgNodeBuf(cLevel);
SIZET cb = 0;
SIZET cKeys = 0;
for (;pcfgkey->cb;) // Watch for empty root node!
{
cb += (SIZET)pcfgkey->cb;
cKeys++;
if (pcfgkey->fLast)
break;
pcfgkey = CfgKeyNext(pcfgkey);
}
if (pcb) // Return total size
*pcb = cb;
return cKeys;
}
//----------------------------------------------------------------------------
// Description: Delete a key from a node.
// On entry, the current key has already been found
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgKeyDelete(void)
{
BYTE abKey[2][KEY_SIZE];
SIZET cWhich = 0;
PCFGKEY pcfgkeyDelete = CfgNodeKey(CfgNodeLevel());
PCFGKEY pcfgkeyInsert = NULL;
PCFGKEY pcfgkey = pcfgkeyDelete;
PCFGKEY pcfgkeySave;
SIZET cLevel;
FPOS fpos;
// While not a leaf node
while (pcfgkey->fposLeft || pcfgkey->fposRight)
{ // Move into child
fpos = pcfgkey->fposLeft ? pcfgkey->fposLeft: pcfgkey->fposRight;
if (!CfgNodeRead(fpos))
return FALSE;
// Move to last key in node
pcfgkey = CfgNodeKey(CfgNodeLevel());
while (!pcfgkey->fLast)
pcfgkey = CfgKeyNext(pcfgkey);
CfgNodeSetKey(CfgNodeLevel(), pcfgkey);
}
for (;;)
{
cLevel = CfgNodeLevel(); // Get pointer to key
pcfgkey = CfgNodeKey(cLevel);
CfgNodeDirty(cLevel); // Mark node as dirty
if (pcfgkey == pcfgkeyDelete)
break;
if (pcfgkeyInsert) // If node inserted was a right child
if (CfgNodePos(cLevel+1) == pcfgkey->fposRight)
{ // Move it up to next level
pcfgkey->fposRight = pcfgkeyInsert->fposLeft;
pcfgkeyInsert->fposLeft = CfgNodePos(cLevel);
pg->cLevel--;
continue;
}
// Get pointer to buffer
pcfgkeySave = (PCFGKEY)abKey[cWhich];
cWhich = !cWhich;
// Save key
memcpy(pcfgkeySave, pcfgkey, (SIZET)pcfgkey->cb);
if (!CfgKeyRemove()) // Remove key from node
return FALSE;
if (pcfgkeyInsert)
{
if (pcfgkeySave->fposLeft && pcfgkeySave->fposRight)
pcfgkeyInsert->fposRight = pcfgkeySave->fposRight;
if (!CfgKeyInsert(cLevel, pcfgkeyInsert))
return FALSE;
}
pcfgkeySave->fposRight = 0;
if (CfgKeyCount(cLevel, NULL) == 0)
{
CfgNodeUsed(cLevel, FALSE);
if (!CfgBlockMarkEmpty(CfgNodePos(cLevel)))
return FALSE;
pcfgkeySave->fposLeft = 0;
}
else
{
pcfgkeySave->fposLeft = CfgNodePos(cLevel);
}
pcfgkeyInsert = pcfgkeySave;
pg->cLevel--;
}
if (pcfgkeyInsert)
if (pcfgkey->fposLeft && pcfgkey->fposRight)
pcfgkeyInsert->fposRight = pcfgkeySave->fposRight;
if (pcfgkey->fposData)
if (!CfgBlockMarkEmpty(pcfgkey->fposData))
return FALSE;
if (!CfgKeyRemove()) // Remove key from node
return FALSE;
if (pcfgkeyInsert)
if (!CfgKeyInsert(cLevel, pcfgkeyInsert))
return FALSE;
if (CfgKeyCount(cLevel, NULL) > 0 || CfgNodeLevel() == 0)
return TRUE;
CfgNodeUsed(cLevel, FALSE);
if (!CfgBlockMarkEmpty(CfgNodePos(cLevel)))
return FALSE;
pg->cLevel--; // Node was completely empty
cLevel = CfgNodeLevel();
CfgNodeDirty(cLevel); // Remove link from parent
pcfgkey = CfgNodeKey(cLevel);
if (CfgNodePos(cLevel + 1) == pcfgkey->fposLeft)
pcfgkey->fposLeft = 0;
else
pcfgkey->fposRight = 0;
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Find a key in the configuration file.
// Parameters: pszName Key name.
// ppcfgkey Variable to receive pointer to key
// This pointer is null if the key value is not
// found.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgKeyFind(PCSZ pcszName, PCFGKEY _FAR_ *ppcfgkey)
{
FPOS fposNode = pg->cfghdr.fposRoot;// Start at root
PCFGKEY pcfgkey;
CfgNodeReset(); // Reset node level information
while (fposNode) // While more nodes to search
{
SIZET cLevel;
if (!CfgNodeRead(fposNode))
return FALSE;
cLevel = CfgNodeLevel();
fposNode = 0;
pcfgkey = CfgNodeKey(cLevel);
for (;pcfgkey->cb;) // Watch for root node empty (cb==0)
{
int rc = strcmp(pcszName, pcfgkey->szName);
if (rc == 0) // Yeah! A match
{
CfgNodeSetKey(cLevel, pcfgkey);
*ppcfgkey = pcfgkey;
return TRUE;
}
else if (rc < 0) // Less than current key
{
fposNode = pcfgkey->fposLeft;
break;
}
else if (rc > 0 && pcfgkey->fLast) // If greater than key and on
{ // key in node, move down to
fposNode = pcfgkey->fposRight; // right child
break;
}
pcfgkey = CfgKeyNext(pcfgkey);
}
CfgNodeSetKey(cLevel, pcfgkey);
}
*ppcfgkey = NULL; // Not found
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Insert a key into the b-plus tree node.
// The current node is guaranteed to have sufficient space
// to insert the new key.
// Parameters: cLevel Level to insert into.
// pcfgkeyInsert Key to insert. This key is in temporary
// storage!
// Returns: TRUE if successful and object found.
//----------------------------------------------------------------------------
static BOOL FN_L CfgKeyInsert(SIZET cLevel, PCFGKEY pcfgkeyInsert)
{
PCFGKEY pcfgkey = (PCFGKEY)CfgNodeBuf(cLevel);
SIZET cb;
CfgKeyCount(cLevel, &cb); // Get size of key
CfgNodeDirty(cLevel); // Cache dirty!
for (;pcfgkey->cb;) // Check that node not empty
{
int rc = strcmp(pcfgkeyInsert->szName, pcfgkey->szName);
Assert(rc != 0);
if (rc < 0)
break;
cb -= (SIZET)pcfgkey->cb; // Number of bytes remaining
if (pcfgkey->fLast) // Insert the split key as the last key
{ // The node that was split must have been
pcfgkey->fposRight = 0; // the right child of the last key
pcfgkey->fLast = FALSE;
pcfgkey = CfgKeyNext(pcfgkey);
memcpy(pcfgkey, pcfgkeyInsert, (SIZET)pcfgkeyInsert->cb);
pcfgkey->fLast = TRUE;
CfgNodeSetKey(cLevel, pcfgkey);
return TRUE;
}
pcfgkey = CfgKeyNext(pcfgkey);
}
//
// When we get here, we are inserting into the middle of
// a node. The node which was split was the left child of the
// key pointed to by 'pcfgkey'.
//
if (pcfgkey->cb && pcfgkeyInsert->fposRight)
{
pcfgkey->fposLeft = pcfgkeyInsert->fposRight;
pcfgkeyInsert->fposRight = 0;
}
pcfgkeyInsert->fLast = FALSE;
if (cb) // Make room for new key
memmove(((PBYTE)pcfgkey) + (SIZET)pcfgkeyInsert->cb, pcfgkey, (SIZET)cb);
else
pcfgkeyInsert->fLast = TRUE; // Very first key in root node!
// Copy new key into buffer
memcpy(pcfgkey, pcfgkeyInsert, (SIZET)pcfgkeyInsert->cb);
CfgNodeSetKey(cLevel, pcfgkey);
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Add a new key to the configuration file.
// The node being created must not exist!
// Parameters: pcszName Key name.
// ppcfgkey Variable to receive pointer to key
// cb Size of data area for key.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgKeyNew(PCSZ pcszName, PCFGKEY _FAR_ *ppcfgkey, SIZET cb)
{
FPOS fposNode = pg->cfghdr.fposRoot;
PCFGKEY pcfgkey;
BYTE bKey[KEY_SIZE];
PCFGKEY pcfgkeyNew = (PCFGKEY)bKey;
SIZET cLevel;
CfgNodeReset(); // Start reading from the top
while (fposNode)
{
SIZET cNode;
if (!CfgNodeRead(fposNode))
return FALSE;
cLevel = CfgNodeLevel();
CfgKeyCount(cLevel, &cNode);
Assert(cNode <= NODE_SIZE);
if (NODE_SIZE - cNode < KEY_SIZE)// Check if we need to split
{
if (!CfgKeySplit()) // Split node
return FALSE;
//
// After a split, simply start reading from the top.
// This is not the most efficient way but it is the simplist.
// It should go fairly fast because all nodes are cached.
// If this we not done, the cache would have to be carefully
// updated to make sure that the node in the search path have
// the highest usage counts -- otherwise they may get discarded.
//
CfgNodeReset();
fposNode = pg->cfghdr.fposRoot;
}
else
{
pcfgkey = CfgNodeKey(cLevel);
fposNode = 0;
if (pcfgkey->cb == 0) // Root node and it's empty
break;
for (;;)
{
int rc = strcmp(pcszName, pcfgkey->szName);
Assert(rc != 0);
if (rc < 0) // Less than current key
{ // Move down to left
CfgNodeSetKey(cLevel, pcfgkey);
fposNode = pcfgkey->fposLeft;
break;
}
else if (rc > 0 && pcfgkey->fLast) // If greater than key and on
{ // key in node, move down to
CfgNodeSetKey(cLevel, pcfgkey); // right child
fposNode = pcfgkey->fposRight;
break;
}
pcfgkey = CfgKeyNext(pcfgkey);
Assert(pcfgkey->cb);
}
}
}
//
// OK, this is where we insert the key. We are in a leaf node.
// Create the key. Leave room for data if possible.
//
memset(pcfgkeyNew, 0, sizeof(CFGKEY));
pcfgkeyNew->cb = KEY_OVERHEAD;
strcpy(pcfgkeyNew->szName, pcszName);
pcfgkeyNew->cb += strlen(pcszName);
pcfgkeyNew->cbData = (LONG)cb;
// If data fits, leave room for it
if ((ULONG)KEY_SIZE - pcfgkeyNew->cb >= cb)
pcfgkeyNew->cb += (LONG)cb;
if (!CfgKeyInsert(cLevel, pcfgkeyNew)) // Insert the key, this will also mark
return FALSE; // the node as dirty
*ppcfgkey = CfgNodeKey(cLevel);
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Remove the current key on the current level.
// This routine assumes that more than one key exists.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgKeyRemove(void)
{
SIZET cLevel = CfgNodeLevel();
PCFGKEY pcfgkey = CfgNodeKey(cLevel);
PCFGKEY pcfgkey1 = (PCFGKEY)CfgNodeBuf(cLevel);
PCFGKEY pcfgkey2;
CfgNodeDirty(cLevel);
if (pcfgkey1 == pcfgkey) // Key to remove is first in list
{
if (pcfgkey1->fLast) // Key to remove was only key in list
{
pcfgkey1->cb = 0;
return TRUE;
}
pcfgkey2 = pcfgkey1;
pcfgkey1 = CfgKeyNext(pcfgkey1);
}
else
{
do
{
pcfgkey2 = pcfgkey1;
pcfgkey1 = CfgKeyNext(pcfgkey1);
}
while (pcfgkey1 != pcfgkey);
if (pcfgkey1->fLast) // Key to remove was last in list
{
pcfgkey2->fLast = TRUE;
return TRUE;
}
pcfgkey2 = pcfgkey1;
pcfgkey1 = CfgKeyNext(pcfgkey1);
}
for (;;)
{
PCFGKEY pcfgkey12 = CfgKeyNext(pcfgkey1);
memmove(pcfgkey2, pcfgkey1, (SIZET)pcfgkey1->cb);
if (pcfgkey2->fLast)
break;
pcfgkey2 = CfgKeyNext(pcfgkey2);
pcfgkey1 = pcfgkey12;
}
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Split the current node.
// Parameters:
// Returns: TRUE if successful and object found.
//----------------------------------------------------------------------------
static BOOL FN_L CfgKeySplit(void)
{
SIZET i;
SIZET cLevel = CfgNodeLevel();
SIZET cKeys = CfgKeyCount(cLevel, NULL);
SIZET n = pg->acLevelCache[CfgNodeLevel()];
SIZET m;
SIZET cM;
PBYTE pbN = pg->apbCache[n];
PBYTE pbM;
SIZET root;
PBYTE pbRoot;
PCFGKEY pcfgkey1, pcfgkey2;
PCFGKEY pcfgkeySplit;
BYTE bKey[KEY_SIZE];
Assert(cKeys >= 3); // Must be at least 3 to split!
if (!CfgNodeNew(&m)) // Create a new node to split into
return FALSE;
pbM = pg->apbCache[m];
Assert(pbM != pbN); // This should not happen!
pg->afCacheDirty[n] = TRUE; // Mark nodes as dirty
pg->afCacheDirty[m] = TRUE;
pcfgkey1 = NULL; // Skip first cKeys/2 keys
pcfgkey2 = (PCFGKEY)pbN; // They remain in this node
for (i = 0; i < cKeys / 2; ++i)
{
pcfgkey1 = pcfgkey2;
pcfgkey2 = CfgKeyNext(pcfgkey2);
}
pcfgkeySplit = pcfgkey2; // Save pointer to key being split
pcfgkey1->fLast = TRUE; // Update new 'last key' in node N
pcfgkey1->fposRight = pcfgkeySplit->fposLeft;
pcfgkey2 = CfgKeyNext(pcfgkey2); // Jump over split key
cM = 0; // Copy last (cKeys-1)/2 keys to new node
for (i = 0; i < (cKeys-1)/2; ++i)
{
memcpy(pbM + (SIZET)cM, (PBYTE)pcfgkey2, (SIZET)pcfgkey2->cb);
cM += (SIZET)pcfgkey2->cb;
pcfgkey2 = CfgKeyNext(pcfgkey2); // Jump over split key
}
// Move split key to temp storage
memcpy(bKey, pcfgkeySplit, (SIZET)pcfgkeySplit->cb);
pcfgkeySplit = (PCFGKEY)bKey;
pcfgkeySplit->fposLeft = pg->afposCache[n];
pcfgkeySplit->fposRight = pg->afposCache[m];
if (CfgNodeLevel() > 0) // If not root, insert split key into
{ // parent node
return CfgKeyInsert(CfgNodeLevel() - 1, pcfgkeySplit);
}
//
// When we get here, we are splitting the root node. The tree
// will be growing by one level.
//
if (!CfgNodeNew(&root)) // Create a new node to split into
return FALSE;
pg->afCacheDirty[root] = TRUE; // Node is dirty
pbRoot = pg->apbCache[root]; // Get pointer to buffer
Assert(pbRoot != pbN); // This should not happen!
Assert(pbRoot != pbM);
pcfgkeySplit->fLast = TRUE; // Copy key to root
memcpy(pbRoot, (PBYTE)pcfgkeySplit, (SIZET)pcfgkeySplit->cb);
pg->fHeaderDirty = TRUE; // Update file header
pg->cfghdr.fposRoot = pg->afposCache[root];
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Lock a configuration file.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgLock(void)
{
if (pg == NULL || !pg->fInit) // Not valid?
return FALSE;
if (pg->tid == THREADID) // Nested request?
{
pg->cLock++;
return TRUE;
}
#if OS_OS2 || OS_PM
if (DosRequestMutexSem(pg->hmtx, -1))
return FALSE;
#endif
pg->cLock = 1;
pg->tid = THREADID;
if (!FileLock(pg->hf, (FPOS)-1L, 0)) // Lock the file
return FALSE;
return CfgHeaderRead(); // Read header
}
//----------------------------------------------------------------------------
// Description: Create a new b-plus tree node.
// The buffer is flushed immediately so that the disk address
// of the node is known.
// Parameters: pc Buffer to receive cache id of new node
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgNodeNew(PSIZET pc)
{
SIZET n;
if (!CfgCacheFindEmpty(&n)) // Get an unused cache buffer
return FALSE;
memset(pg->apbCache[n],0,NODE_SIZE);// Zero
*pc = n; // Flush it to disk
return CfgCacheFlush(n);
}
//----------------------------------------------------------------------------
// Description: Read a b-plus tree node from the disk.
// This node is assumed to be the node for the next level in
// the tree.
// Parameters: fpos Offset of node.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgNodeRead(FPOS fpos)
{
SIZET i;
BOOL fFound = FALSE;
PBYTE pb;
SIZET cb;
Assert(pg->cLevel < MAX_LEVEL); // Can't read any deeper!
for (i = 0; i < MAX_CACHE; ++i) // Search for node in cache
if (pg->afCacheUsed[i] && pg->afposCache[i] == fpos)
{
fFound = TRUE; // Increment usage count!
pg->alCacheUse[i] = ++pg->lCacheUse;
break;
}
if (!fFound)
{
if (!CfgCacheFindEmpty(&i)) // Get an unused cache buffer
return FALSE;
pg->afposCache[i] = fpos; // Set cache disk location
pb = pg->apbCache[i]; // Read node from disk
cb = 0;
if (!CfgDataRead(fpos, NULL, &pb, &cb))
{
pg->afCacheUsed[i] = FALSE; // Free cache buffer on failure!
return FALSE;
}
Assert(cb == NODE_SIZE); // Validate size
}
pg->acLevelCache[pg->cLevel] = i; // Store level information
pg->apcfgkeyLevel[pg->cLevel] = (PCFGKEY)CfgNodeBuf(pg->cLevel);
pg->cLevel++; // Move to next level
return TRUE;
}
//----------------------------------------------------------------------------
// Description: Reset the current level pointer to the b-plus tree. This
// function should be called before reading in the root node.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static VOID FN_L CfgNodeReset(void)
{
pg->cLevel = 0;
return ;
}
//----------------------------------------------------------------------------
// Description: Open a configuration file. Create on if necessary.
// Parameters: pcsz Configuration file name.
// If null, the base application name is used.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgOpen(PCSZ pcsz)
{
CHAR szFile[MAX_PATH];
USHORT fs = FL_OPEN|FL_CREATE|FL_READWRITE|FL_DENYNONE|FL_BINARY;
FPOS flen;
if (pg && pg->fInit) // Already open?
return TRUE;
if (!CfgInitialize()) // Initialize
{
CfgClose();
return FALSE;
}
//
// Get application name
// Use strcat to append an extension. The DOS application name
// is gauranteed not to have an extension. The UNIX file name may have
// an extension -- but it should be retained (a.out --> a.out.cfg).
// Check if file area is writeable before attempting to create.
//
if (pcsz && pcsz[0]) // Use supplied name
strcpy(szFile, pcsz);
else // Use base application name
strcpy(szFile, CommandLineAppName());
if (!szFile[0]) // Default name
strcpy(szFile, "config");
strcat(szFile, ".cfg");
if (!FnameQualify(szFile, NULL, NULL, FNAME_APP_DIR))
return FALSE;
if (!FnameIsFile(szFile)) // If it doesn't exist, create it in
if (FnameIsWriteable(szFile))
CfgCreate(szFile); // the application directory.
if (!FnameIsFile(szFile)) // If was not created, then
{ // try to put it in the current directory
if (!FnameQualify(szFile, NULL, NULL, FNAME_CUR_DIR))
return FALSE;
if (!FnameIsFile(szFile)) // Wasn't created!
if (FnameIsWriteable(szFile))
CfgCreate(szFile);
}
if (!FnameIsFile(szFile)) // Wasn't created!
return FALSE;
//
// OK, we have successfully initialized and we have a valid
// file name. Go ahead a finish opening the data file.
//
if (!FileOpen(&pg->hf, szFile, fs, NULL))
return FALSE;
flen = FileGetSize(pg->hf); // Get configuration file size
if (flen < HDR_SIZE) // Double check that is valid
{
if (flen >= 0)
{
Error("Internal configuration file error.\n"
"Configuration file has incorrect size.");
}
goto ERROR_EXIT;
}
if (CfgLock())
{
#if COMPILE_DEBUG
CfgBlockWalk(FALSE);
#endif
Log(LOG_MEDIUM, "Configuration file opened.\n'%s'", szFile);
BaseExitFunc((PFNEXIT)CfgClose, SYS_EXIT_PRIORITY);
return CfgUnlock();
}
ERROR_EXIT:
CfgClose();
return FALSE;
}
//----------------------------------------------------------------------------
// Description: Read from a configuration file.
// Parameters: pcszName Name of the object to read.
// ppb Variable to receive address of data.
// If null, a buffer is allocated for the data.
// The caller is responsible for freeing the data
// buffer.
// pcb Variable to receive size of data.
// If not null, the initial value of this variable
// should be the size of the data data buffer or
// 0 for the maximum size.
// If null, size is not returned.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgRead(PCSZ pcszName, PBYTE _FAR_ *ppb, PSIZET pcb)
{
BOOL fResult = FALSE;
PCFGKEY pcfgkey;
Assert(pcszName && strlen(pcszName) <= KEY_NAME);
Assert(ppb);
if (!CfgLock())
return FALSE;
if (!CfgKeyFind((PSZ)pcszName, &pcfgkey))
goto ERROR_EXIT;
if (pcfgkey == NULL)
{
Error("Configuration file key not found\n'%s'", pcszName);
goto ERROR_EXIT;
}
if (pcfgkey->fposData) // If data is in a separate block,
{ // read it in from disk
if (!CfgDataRead(pcfgkey->fposData, NULL, ppb, pcb))
goto ERROR_EXIT;
}
else
{
SIZET cb = (SIZET)pcfgkey->cbData; // Allocate buffer for data
if (pcb && *pcb)
cb = MIN(*pcb, cb);
if (pcb)
*pcb = cb;
if (*ppb == NULL) // Allocate a buffer
{
*ppb = (PBYTE)MemAlloc(cb);
if (*ppb == NULL)
return FALSE;
}
memcpy(*ppb, pcfgkey->szName + strlen(pcfgkey->szName) + 1, cb);
}
fResult = TRUE;
ERROR_EXIT:
if (!CfgUnlock())
return FALSE;
return fResult;
}
//----------------------------------------------------------------------------
// Description:
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
VOID FN_E CfgSetCache(SIZET cBuffers)
{
pg->cCacheMin = MIN(cBuffers, MAX_CACHE);
if (pg->fInit)
CfgCacheShrink();
return ;
}
//----------------------------------------------------------------------------
// Description: Unlock a configuration file.
// Parameters:
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
static BOOL FN_L CfgUnlock(void)
{
BOOL fResult = TRUE;
if (pg->cLock == 0) // If not locked, ignore request
return FALSE;
pg->cLock--; // Remove a lock
if (pg->cLock) // Still locked?
return TRUE; // Then done
if (!CfgCacheFlushAll()) // Flush cache buffers
fResult = FALSE;
if (!CfgCacheShrink()) // Shrink cache
fResult = FALSE;
if (!CfgHeaderWrite(FALSE)) // Write header if dirty
fResult = FALSE;
if (!FileFlush(pg->hf)) // Flush data to disk
fResult = FALSE;
if (!FileUnlock(pg->hf, (FPOS)-1L, 0)) // Unlock the file
fResult = FALSE;
#if OS_OS2 || OS_PM
if (DosReleaseMutexSem(pg->hmtx)) // Release semaphore
fResult = FALSE;
#endif
pg->tid = 0;
return fResult;
}
//----------------------------------------------------------------------------
// Description: Write to a configuration file.
// NOTE: Keys are never expanded to hold more data. If the
// data does not fit, it is placed in a separate block.
// Also, keys are never shrunk to recover slack data
// space. The space remains in case it is re-used.
// Parameters: pcszName Name of the object to write.
// ppb Pointer to object.
// cb Size of object.
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
BOOL FN_E CfgWrite(PCSZ pcszName, PBYTE pb, SIZET cb)
{
BOOL fResult = FALSE;
SIZET cData;
PCFGKEY pcfgkey;
Assert(pcszName && strlen(pcszName) <= KEY_NAME);
Assert(pb && cb);
if (!CfgLock()) // Lock
return FALSE;
// Try to find key
if (!CfgKeyFind(pcszName, &pcfgkey))
goto ERROR_EXIT;
if (pcfgkey == NULL)
if (!CfgKeyNew(pcszName, &pcfgkey, cb))
goto ERROR_EXIT;
CfgNodeDirty(CfgNodeLevel()); // Cache buffer is dirty!
cData = (SIZET)pcfgkey->cb;
cData -= KEY_OVERHEAD;
cData -= strlen(pcfgkey->szName);
pcfgkey->cbData = (LONG)cb;
if (cData >= cb) // Data fits within key!
{
if (pcfgkey->fposData) // Release existing data block
if (!CfgBlockMarkEmpty(pcfgkey->fposData))
goto ERROR_EXIT;
pcfgkey->fposData = 0; // Copy data into key
memcpy(pcfgkey->szName + strlen(pcfgkey->szName) + 1, pb, cb);
}
else // Else write data into a separate
{ // block.
pcfgkey->fposData = CfgDataWrite(pcfgkey->fposData, pb, (LONG)cb);
if (pcfgkey->fposData < 0)
goto ERROR_EXIT;
}
fResult = TRUE;
ERROR_EXIT:
if (!CfgUnlock()) // Unlock before leaving!
fResult = FALSE;
return fResult;
}
//----------------------------------------------------------------------------
// Description: Run standard test suite
// Parameters: sTest Test to run.
// 0 Run all default tests (except).
// Returns: TRUE if successful.
//----------------------------------------------------------------------------
#if COMPILE_TEST
BOOL FN CfgTest(SHORT sTest)
{
static CHAR szName[MAX_CFG_NAME+1];
static CHAR szNamePrev[MAX_CFG_NAME+1];
static BYTE bData[4096];
static PSZ apsz[] =
{
"5a",
"5ab",
"5abc",
"5abcd",
"5abcde",
"5abcdef",
"5abcdefg",
"5abcdefgh",
"5abcdefghi",
"5abcdefghij",
"5abcdefghijk",
"5abcdefghijkl",
"5abcdefghijklm",
"5abcdefghijklmn",
"5abcdefghijklmno",
"5abcdefghijklmnop",
"5abcdefghijklmnopq",
"5abcdefghijklmnopqr",
"5abcdefghijklmnopqrs",
"5abcdefghijklmnopqrst",
"5abcdefghijklmnopqrstu",
"5abcdefghijklmnopqrstuv",
"2a",
"2ab",
"2abc",
"6a",
"6ab",
"6abc",
"7ab",
"3ab",
"7a",
"3a",
"7abc",
"3abc",
"4a",
"4abc",
"4ab",
"4abcde",
"4abcd",
"4abcdefg",
"4abcdef",
"4abcdefgh",
"4abcdefghi",
"4abcdefghijk",
"4abcdefghijklmno",
"4abcdefghijklmnopq",
"4abcdefghijklmnopqrst",
"4abcdefghijklmnopqrstu",
"4abcdefghijklmnopqr",
"4abcdefghijklmnopqrs",
"4abcdefghij",
"4abcdefghijklmnop",
"4abcdefghijkl",
"4abcdefghijklm",
"4abcdefghijklmnopqrstuv",
"4abcdefghijklmn",
NULL
};
SIZET i,j;
SIZET cb;
PBYTE pb;
SIZET cElems = 0;
if (!CfgOpen(NULL))
return FALSE;
if (sTest == 1)
{
Output("Show all configuration file keys.\n");
szNamePrev[0] = '\0';
if (CfgFindFirst(szName))
{
do
{
Output(" %-.70s\n", szName);
cElems++;
if (strcmp(szNamePrev, szName) >= 0)
{
Error("Invalid key sort sequence.");
return FALSE;
}
strcpy(szNamePrev, szName);
}
while (CfgFindNext(szName));
CfgFindClose();
}
Output("%u elements.\n", cElems);
return TRUE;
}
if (sTest == 2)
{
LONG l = 0;
SIZET i;
Output("Write some simple key with no data.\n");
for (i = 0; i < 26; ++i)
{
sprintf(szName, "%c", 'a' + i);
if (!CfgWrite(szName, (PBYTE)&l, sizeof(l)))
return FALSE;
}
}
if (sTest == 3)
{
SIZET i;
Output("Write some simple key with lots of data.\n");
for (i = 0; i < 26; ++i)
{
sprintf(szName, "%c", 'A' + i);
if (!CfgWrite(szName, (PBYTE)bData, 200))
return FALSE;
}
}
if (sTest == 4)
{
Output("Configuration file standard test suite.\n");
for (i = 0; apsz[i]; ++i)
{
memset(bData, i, i + 1);
if (!CfgWrite(apsz[i], bData, i + 1))
return FALSE;
}
for (i = 0; apsz[i]; ++i)
{
cb = sizeof(bData);
pb = bData;
if (!CfgRead(apsz[i], &pb, &cb))
continue;
if (cb != i + 1)
Output("Key '%s' has incorrect size.\n", apsz[i]);
for (j = 0; j < i + 1; ++j)
if (bData[j] != i)
{
Output("Key '%s' data is corrupt.\n", apsz[i]);
break;
}
}
}
if (sTest == 5)
{
Output("Delete all elements from configuration file.\n");
while (CfgFindFirst(szName))
{
CfgFindClose();
cElems++;
if (!CfgDelete(szName))
return FALSE;
}
Output("%u elements deleted.\n", cElems);
}
if (sTest > 5)
{
Output("Random configuration file stress test.\n");
if (CfgFindFirst(szName)) // Count initial elements
{
do
{
cElems++;
}
while (CfgFindNext(szName));
CfgFindClose();
}
for (i = 5; i < (SIZET)sTest; ++i)
{
SIZET cOp = Random(0, 7);
SIZET cData = 1;
Output(".");
if (!cElems) // If no elements, write some more
cOp = 4;
if (cOp <= 2) // Existing
{ // Pick a name at random
SIZET cWhich = Random(0, cElems - 1);
BOOL fFound = FALSE;
if (CfgFindFirst(szName))
{
do
{
if (cWhich == 0)
{
fFound = TRUE;
break;
}
cWhich--;
}
while (CfgFindNext(szName));
CfgFindClose();
}
if (!fFound)
Output("\n%u elements.\n", cElems);
Assert(fFound);
}
if (cOp == 3)
strcpy(szName, "$$$$$$$");
if (cOp > 3)
{ // Create a name at random
SIZET cLen = Random(1, MAX_CFG_NAME);
for (j = 0; j < cLen; ++j)
szName[j] = (CHAR)('A' + Random(0,25));
szName[j] = '\0';
cData = Random(1, sizeof(bData));
if (cData <= 4) // Don't bother
memset(bData, 0, cData);
else
{
for (j = 4; j < cData; ++j)
bData[j] = (BYTE)Random(0,255);
*((PCRC)bData) = CrcCalc(bData + 4, cData - 4);
}
}
switch (cOp)
{
case 0: // Read existing
cb = sizeof(bData);
pb = bData;
if (!CfgRead(szName, &pb, &cb))
Output("\nProblem reading existing.");
else if (cb > 4)
{
CRC crc1 = CrcCalc(bData + 4, cb - 4);
CRC crc2 = *((PCRC)bData);
if (crc1 != crc2)
Output("\nData file CRC is corrupt.");
}
break;
case 1: // Delete existing
if (!CfgDelete(szName))
Output("\nProblem deleting existing.");
cElems--;
break;
case 2: // Find existing
if (!CfgFind(szName))
Output("\nExpected to find name.");
break;
case 3: // Find not exist
if (CfgFind(szName))
Output("\nDid not expect to find name.");
break;
default: // Write
if (!CfgFind(szName)) // If it doesn't already exist
cElems++; // increment element count
if (!CfgWrite(szName, bData, cData))
Output("\nProblem writing data element (%d) '%s'.", cOp, szName);
break;
}
}
Output("\n");
Output("%u elements.\n", cElems);
if (CfgFindFirst(szName)) // Count final elements
{
do
{
cElems--;
}
while (CfgFindNext(szName));
CfgFindClose();
}
if (cElems)
{
Output("Incorrect number of keys remain in file.\n");
return FALSE;
}
}
// Default test!
Output("Configuration file walk blocks.\n");
CfgBlockWalk(TRUE);
CfgClose();
return TRUE;
}
#endif
//----------------------------------------------------------------------------
//------------------------------- End of File --------------------------------
//----------------------------------------------------------------------------